/*
 * $Id: GalSARReader.java 8 2009-06-13 18:01:14Z tjoneslow $
 * Copyright 2005 Softstart Services, Inc. All rights reserved. 
 */

package stellar.io;
import stellar.data.GroupRecord;
import stellar.data.HexID;
import stellar.data.ProviderRecord;
import stellar.data.TableRecord;
import stellar.data.TableRowRecord;
import stellar.data.GroupType;
import stellar.dialog.EditOptions;
import stellar.dialog.ViewTableData;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import stellar.data.Astrogation;
import stellar.data.TableRecordKey;

import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
import org.jibx.runtime.JiBXException;

import stellar.MapPreferences;

/**
 * Galactic Sector Archive reader. Galactic is an older DOS based Traveller
 * mapping program. While in use, Galactic keeps all of it's data is fixed format
 * files layed out in a specific directory structure. In order to allow people
 * to transport this around Galactic has the option of creating an archive. This
 * archive is either on a sector by sector basis or of the full data set.
 * <P>
 * The Sector archive is kept in a .SAR (Sector Archive) file, which is
 * a PKZip file with a non-standard name. Galactic ships with an old freeware
 * version of PKZip used to access these SAR files.
 * <P>
 * This class uses the <code>java.util.zip</code> package to access a SAR file
 * and read the fixed structure and files into a new <code>Astrogation</code>.
 * This is a one-way conversion, there is no way (currently) to create or update
 * the Galactic SAR files with new data.
 * <P>
 * The Galactic SAR file structure is as follows:
 * <P>
 * <strong>GALS\[GALAXY]\[SECTOR]</strong>: By default all files are preceeded
 * by this directory. The GALS directory is where Galactic keeps the data
 * for all the Galaxies. Each Galaxy (a set of data files) is then divided into
 * Sectors. By convention the SAR file name and the [SECTOR] name should be the
 * same, and this code assumes this is true.
 * <P>
 * <strong>[SECTOR].DAT</strong>: This file contains several types of data
 * about to the sector. <br>
 * Line 1: Full display name of the sector.<br>
 * Line 3-18: Subsector information. These are fixed format records
 * <br><PRE>X: [NAME]                    [Filename]        [flag]</PRE>
 * where X is the single letter subsctor identifier (A-P). [NAME] is the
 * full name of the subsector. [Filename] is the name of the subsector data
 * file (see below). [flag] is an optional single letter code of 'm' or 'f',
 * indicating additional information about the subsector.<br>
 * Line 20: Base information. This section starts with a line "Bases:". The
 * base information has the following information format:
 * <br><PRE>X = [NAME]</PRE>
 * where X is a single letter code for the base which may be a space, and [NAME]
 * is the full name of the base type. This information is loaded into the local
 * references file. There may be no information in this section. This section is
 * terminated by a blank line. <br>
 * Line 22: Allegiance information. This section starts with a line "Allegiance:".
 * The allegiance information has the following information format:
 * <br><pre>CC NN = [NAME]</pre>
 * where CC is a two digit color number. This color number is converted to a
 * Stellar color number via the gal.color table in the global references data.
 * NN is a two character allegiance code. Many of these codes are already in
 * the global reference file, but users may have created their own. [NAME] is
 * the full name of the allegiance. This information is loaded into the local
 * reference information. There may be no information in this section. This
 * section is terminated by a blank line.
 * <P>
 * <strong>UWP.DAT</strong>: This is an optional file which contains the text
 * meanings of the UWP values in the data files. This class ignores this file.
 * <P>
 * <strong>CREDITS.TXT</strong>: An optional file containing some text comments
 * about the sector. This file is read and placed in the <code>GroupRecord</code>
 * comments field for the sector.
 * <P>
 * <strong>TMP.TMP</strong>: This is a temporary file generated by the Galactic
 * when it creates the SAR file about where the SAR file came from. This file
 * contains four lines:
 * <br>line 1: the version of galactic generating the SAR file. Usually 2.4
 * <br>line 2: the galaxy of this sar file, same as the [GALAXY] in the path.
 * <br>line 3: the sector of this sar file, same as the [SECTOR] in the path.
 * <br>line 4: The [GALAXY].LST entry. This line contains a sector short name,
 * sector full name, an x,y location and a color code (A-P).
 * <br> This file is currently ignored.
 * <P>
 * <strong>[SECTOR].SEC</strong>: An optional Sector file. Since this may be out
 * of date, it is ignored.
 * <p>
 * <strong>MAP\SUB_A.DAT</strong>: The subsector data files. The default names
 * for these files is SUB_X.DAT, where X is the subsector letter, but they may
 * have any valid name. These names are specified in the [SECTOR].DAT file above.
 * These files are read by the {@link GalDATReader}.
 * <p>
 * <strong>SYS\S0101.SYS</strong>: The SYS directory holds the system files, each
 * file contains a formated file with the list of planets and stars in a system.
 * None of these files are currently used.
 * <p>
 * <strong>LOC\P0101.TXT</strong>: The LOC directory holds either subsector or
 * planet comment files, each file is a text file with comments about the
 * subsector or specific world. These files are read into the comments field of
 * the subsector <code>GroupRecord</code> or <code>StarSystem</code>.
 * <p>
 * <strong>HEX\P0101.HEX</strong>: the HEX directory holds planetary hex maps
 * from a maping subsystem within Galactic. These files are currently ignored.
 * <p>
 * <strong>GEN\[SECTOR].MNU</strong>: The GEN directory holds additional
 * information about the sector. These are all text files and currently ignored.
 * <p>
 * <strong>*.MNU</strong>: These are menu files, and may appear in any directory.
 * Menu system is a crude text only hypertext system included with Galactic. The
 * Menu files are a set of formatted links to other files. All MNU files are
 * currently ignored, though conversion to HTML should not be difficult.
 *
 * @version $Revision: 1.9 $
 * @author $Author: tjoneslow $
 */
public class GalSARReader implements AstrogationFileReader
{
    private String fileName;
    private File   inputFile;
    private Astrogation data;
    private TableRecord colorTable;
    
    /**
     * Constructor, with a file name to read.
     * @param inputFile
     */
    public GalSARReader(String inputFile)
    {
        fileName = inputFile;
        this.inputFile = new File (inputFile);
    }
    
    /**
     * Constructor with a <code>File</code> to read. 
     * @param inputFile
     */
    public GalSARReader (File inputFile)
    {
        this.inputFile = inputFile;
        fileName = inputFile.getAbsolutePath();
    }
    
    /**
     * Read the file specified in the constructor. Generates a <code>Astrogration</code>
     * @throws org.jibx.runtime.JiBXException Only if reading the global
     * references file for the new <code>Astrogration</code> class fails
     * because the file is corrupt. 
     * @throws SECFileStateMachineException if parsing 
     * the subsector DAT files fails because of corrupt or unexpected data.
     * @throws java.io.IOException if reading the SAR file causes an IO error
     * @throws java.io.FileNotFoundException if the file specified in the
     * constructor can not be foune
     */
    public void read () throws FileNotFoundException, IOException, 
    SECFileStateMachineException, JiBXException
    {
        data = new Astrogation(MapPreferences.getInstance().getExternalRefsFileName());
        String galaxy, sectorName, pathName, dirName;
        ZipEntry entry;

        ZipFile readfile = new ZipFile (inputFile);
        
        sectorName = inputFile.getName();
        sectorName = sectorName.substring(0, sectorName.lastIndexOf("."));

        entry = (ZipEntry)(readfile.entries().nextElement());
        galaxy = entry.getName();
        galaxy = galaxy.substring(5);
        galaxy = galaxy.substring(0, galaxy.indexOf("/"));
        
        dirName = "GALS/" + galaxy + "/" + sectorName;
        entry = readfile.getEntry(dirName + "/" + sectorName + ".DAT");
        parseDATFile (readfile.getInputStream(entry));
        
        //Read Credits.txt into the Sector comments. 
        entry = readfile.getEntry(dirName + "/" + "CREDITS.TXT");
        if (entry != null)
        {
            data.getGroup (0).setComment(readTextFile(new BufferedReader (new InputStreamReader (readfile.getInputStream(entry)))));
        }
        // Read Subsectors .dat files one at a time. 
        pathName = dirName + "/MAP/";
        readMapFiles (readfile, pathName);
        data.renumberLinks();
        
        // Read system .txt files one at a time
        
        pathName = dirName + "/LOC";
        readLocFiles (readfile, pathName);
        
        data.postRead();
    }
    
    private void parseDATFile (InputStream datFile) throws IOException
    {
        String line;
        GroupRecord sector, subsector; 
        BufferedReader r = new BufferedReader(new InputStreamReader(datFile));
        ProviderRecord provider = new ProviderRecord("gal", "Galactic 2.4 by Jim Vassilakos", "Galactic 2.4");
        data.setProvider(provider);
        data.setDefaultProvider(provider);
   
        line = r.readLine(); // Sector name
        sector = new GroupRecord();
        sector.setName(line);
        sector.setType(GroupType.SECTOR);
        sector.setLocation(new HexID ("0101"));
        sector.setKey(sector.getName().substring(0,4).toLowerCase() + ".0000");
        sector.setValue(sector.getName());
        sector.setProvider(provider);
        sector.getLocation().setHexGroup(sector);
        sector.getLocation().setHexType(GroupType.GROUP);
        sector.setExtentX(32);
        sector.setExtentY(40);
        data.addGroup(sector);
        
        line = r.readLine(); // blank line
        // 16 subsectors
        for (int i = 0; i < 16; i++)
        {
            line = r.readLine();
            subsector = new GroupRecord();
            subsector.setName(line.substring(2,29).trim());
            subsector.setType(GroupType.SUBSECTOR);
            subsector.setLocation(new HexID((i%4) + 1, (i/4) + 1));
            subsector.setKey(sector.getName().substring(0,4).toLowerCase() + ".01" + i);
            subsector.setValue(subsector.getName());
            subsector.setProvider(provider);
            subsector.getLocation().setHexGroup(sector);
            subsector.getLocation().setHexType(GroupType.GROUP);
            subsector.setParent(sector);
            // This indicates the subsector has a data file (f/m) 
            // with more data assoiated with it.
            if (line.length() > 49) 
            {
                subsector.setFileName(line.substring(29,49).trim());
            }
            else
            {
                subsector.setFileName(line.substring(29).trim());
            }
            subsector.setExtentX(8);
            subsector.setExtentY(10);
            data.addGroup(subsector);
        }
        line = r.readLine(); // blank line
        
        //bases - skipped
        line = r.readLine();
        
        //assert (line.equals("Bases:"));
        TableRecord basesTable = new TableRecord();
        basesTable.setProvider(provider);
        basesTable.setKey (TableRecordKey.BASES);
        basesTable.setValue(sector.getName() + " " + TableRecordKey.BASES);
        int i = 0;
        while (!(line = r.readLine()).equals(""))
        {
            if (line.substring(0,1).equals(" ")) continue;
            TableRowRecord base = new TableRowRecord();
            base.setProvider(provider);
            base.setCode(line.substring(0,1));
            base.setValue(line.substring(4));
            basesTable.add(i,base);
            i++;
        }
        data.getLocalReferences().addTable(basesTable);

        //alegiances - skipped
        line = r.readLine();
        //assert line.equals ("Allegiance:");
        TableRecord politiesTable = new TableRecord();
        politiesTable.setProvider(provider);
        politiesTable.setKey (TableRecordKey.POLITIES);
        politiesTable.setValue(sector.getName() + " " + TableRecordKey.POLITIES);
        i = 0;
        try {
        while (!(line = r.readLine()).equals(""))
        {
            TableRowRecord polity = new TableRowRecord();
            polity.setProvider(provider);
            polity.setColor(getGalacticColor(line.substring(0,2)));
            polity.setCode (line.substring (3,5));
            polity.setValue (line.substring(8));
            politiesTable.add(i, polity);
            i++;
        }}
        catch (NullPointerException ex) {;}// End of File indicator. 
        data.getLocalReferences().addTable(politiesTable);
    }
    
    private void readMapFiles(ZipFile readfile, String pathName) 
        throws IOException, FileNotFoundException, SECFileStateMachineException, JiBXException
    {
        ZipEntry entry;
        
        for (int i=1; i <=16; i++)
        {
            GroupRecord subsector = data.getGroup(i);
            fileName = pathName + subsector.getFileName().toUpperCase();
            entry = readfile.getEntry(fileName);
            GalDATReader reader = new GalDATReader (new BufferedReader(new InputStreamReader(readfile.getInputStream(entry))), data);
            reader.read();
        }
    }
    
    private void readLocFiles (ZipFile readfile, String pathName)
        throws IOException
    {
        Enumeration entries;
        ZipEntry entry;
        String fileName;
        HexID location;
        BufferedReader input;
        
        entries = readfile.entries();
        
        while (entries.hasMoreElements())
        {
            entry = (ZipEntry)entries.nextElement();
            fileName = entry.getName();
            //Found something in the loc directory
            if (!entry.isDirectory() &&
                entry.getName().substring(0, entry.getName().lastIndexOf("/")).equals(pathName))
            {
                
                fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
                if (fileName.charAt(0) == 'P') //system file
                {
                    location = new HexID(fileName.substring(1,5));
                    input = new BufferedReader(new InputStreamReader(readfile.getInputStream(entry)));
                    data.getSystem(location).setComment(readTextFile(input));
                    
                }
                else if (fileName.charAt(0) == 'S') //subsector data
                {
                    input = new BufferedReader(new InputStreamReader (readfile.getInputStream(entry)));
                    int index = fileName.charAt(4) - 'A' + 1;
                    data.getGroup(index).setComment(readTextFile(input));
                }
            }
        }
    }

    private HTMLDocument readTextFile (BufferedReader input)
    {
        int lastBlockStartPos;
        String comment;
        StringBuffer fullComment = new StringBuffer();
        SimpleAttributeSet emptyAttributes = new SimpleAttributeSet();
        HTMLDocument doc = null;
        HTMLEditorKit kit = null;
        try
        {
            while ((comment = input.readLine()) != null)
            {
                fullComment.append(comment);
                fullComment.append("\n");
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
            if (fullComment.length() == 0) return null;
        }
                    
        try
        {
            kit = new HTMLEditorKit ();
            doc = (HTMLDocument)kit.createDefaultDocument();
            ParserCallback reader = doc.getReader(0);
            lastBlockStartPos = 0;
            reader.handleStartTag(HTML.getTag("html"), emptyAttributes, lastBlockStartPos);
            lastBlockStartPos += 6;
            reader.handleStartTag (HTML.getTag("body"), emptyAttributes, lastBlockStartPos);
            lastBlockStartPos += 6;
            reader.handleStartTag (HTML.getTag("pre"), emptyAttributes, lastBlockStartPos);
            lastBlockStartPos += 5;
            reader.handleText(fullComment.toString().toCharArray(), lastBlockStartPos);
            lastBlockStartPos += 1;
            reader.flush();                    
        }
        catch (BadLocationException e)
        {
            e.printStackTrace();
            doc = null;
        }
        try
        {
            input.close();
        }
        catch (IOException e)
        {
            ;//Do Nothing - can't close the input file? 
        }

        return doc;
    }

    private String getGalacticColor(String color)
    {
        if (colorTable == null) 
            colorTable = ViewTableData.getInstance().getGlobalReferences().getTable("gal.color"); 
        if (colorTable != null && color != null)
        {
            String key = TableRecordKey.COLOR.toString() + "." + color;
            return colorTable.getRecord (key).getCode();
        }
        return null;
    }

    /**
     * Gets the parsed data from the SAR file in <code>Astrogation</code>.
     * @return the data, or <code>null</code> if not yet read. 
     */
    public Astrogation getAstrogation() { return data; }
    
    /**
     * A test run menthod, reads the file in, but otherwise does nothing with it.
     * @param args file name of the SAR file to read. 
     */
    public static void main(String[] args)
    {
        GalSARReader readSECFile;
        if (args.length > 0)
            readSECFile = new GalSARReader (args[0]);
        else
            readSECFile = new GalSARReader("C:\\thom\\projects\\cartography\\MARANAN.SAR");
        try
        {
            readSECFile.read();
        }
        catch (Exception ex) { ex.printStackTrace(); }
    }
}
